package simpledb.storage;

import simpledb.common.Type;
import simpledb.common.Utility;

import java.io.*;
import java.util.Arrays;
import java.util.List;

/**
 * HeapFileEncoder 是一个工具类，用于将文本格式的数据文件（如CSV）或者内存中的元组列表转换为 SimpleDB 的二进制堆文件格式。
 *
 * 转换后的数据会按照 SimpleDB 中 HeapPage 和 HeapFile 的要求进行分页存储，每一页固定大小，
 * 多个页面连续写入到输出文件中。
 */
public class HeapFileEncoder {

    /**
     * 将指定的元组列表（仅包含整数字段）转换为二进制页面文件。
     * <br>
     * 输出文件的格式遵循 HeapPage 和 HeapFile 的规范。
     *
     * @see HeapPage
     * @see HeapFile
     * @param tuples 元组列表，每个元组由整数组成
     * @param outFile 输出文件路径
     * @param npagebytes 每页字节数
     * @param numFields 每个元组的字段数量
     * @throws IOException 如果临时文件/输出文件无法打开
     */
    public static void convert(List<List<Integer>> tuples, File outFile, int npagebytes, int numFields)
                                                                                                       throws IOException {
        // 创建一个临时输入文件并写入元组数据（以逗号分隔）
        File tempInput = File.createTempFile("tempTable", ".txt");
        tempInput.deleteOnExit(); // 测试结束后自动删除

        BufferedWriter bw = new BufferedWriter(new FileWriter(tempInput));
        for (List<Integer> tuple : tuples) {
            int writtenFields = 0;
            for (Integer field : tuple) {
                writtenFields++;
                if (writtenFields > numFields) {
                    throw new RuntimeException("Tuple has more than " + numFields + " fields: ("
                                               + Utility.listToString(tuple) + ")");
                }
                bw.write(String.valueOf(field));
                if (writtenFields < numFields) {
                    bw.write(',');
                }
            }
            bw.write('\n');
        }
        bw.close();

        // 使用临时文件作为输入继续转换
        convert(tempInput, outFile, npagebytes, numFields);
    }

    /**
     * 重载方法：使用默认字段类型 Type.INT_TYPE
     */
    public static void convert(File inFile, File outFile, int npagebytes, int numFields) throws IOException {
        Type[] ts = new Type[numFields];
        Arrays.fill(ts, Type.INT_TYPE); // 所有字段默认为 INT 类型
        convert(inFile, outFile, npagebytes, numFields, ts);
    }

    /**
     * 重载方法：使用默认字段分隔符 ','
     */
    public static void convert(File inFile, File outFile, int npagebytes, int numFields, Type[] typeAr)
                                                                                                       throws IOException {
        convert(inFile, outFile, npagebytes, numFields, typeAr, ',');
    }

    /**
     * 将指定的文本文件内容转换为二进制堆文件格式。
     * 输入文件格式假设为：<br>
     * int,...,int\n<br>
     * int,...,int\n<br>
     * ...<br>
     * 每行代表一个元组。<br>
     * <p>
     * 输出文件格式遵循 HeapPage 和 HeapFile 的规范。
     *
     * @see HeapPage
     * @see HeapFile
     * @param inFile 输入文件
     * @param outFile 输出文件
     * @param npagebytes 每页字节数
     * @param numFields 每个元组的字段数量
     * @param typeAr 字段类型数组（如 INT、STRING）
     * @param fieldSeparator 字段分隔符（默认是逗号 ','）
     * @throws IOException IO异常或遇到格式错误的输入行
     */
    public static void convert(File inFile, File outFile, int npagebytes, int numFields, Type[] typeAr,
                               char fieldSeparator) throws IOException {

        // 计算每个记录占用的字节数（根据字段类型）
        int nrecbytes = 0;
        for (int i = 0; i < numFields; i++) {
            nrecbytes += typeAr[i].getLen();
        }

        // 计算每页能容纳的最大记录数
        int nrecords = (npagebytes * 8) / (nrecbytes * 8 + 1); // floor division

        // header bits per record: 每条记录占1位，计算header所需字节数
        int nheaderbytes = (nrecords / 8);
        if (nheaderbytes * 8 < nrecords)
            nheaderbytes++; // 向上取整
        int nheaderbits = nheaderbytes * 8;

        // 读取输入文件和写入输出文件
        BufferedReader br = new BufferedReader(new FileReader(inFile));
        FileOutputStream os = new FileOutputStream(outFile);

        // 缓冲区用于读取字段值
        char[] buf = new char[1024];

        int curpos = 0; // 当前缓冲区位置
        int recordcount = 0; // 当前页已写入记录数
        int npages = 0; // 已写入页数
        int fieldNo = 0; // 当前处理的字段编号

        // header 和 page 数据流
        ByteArrayOutputStream headerBAOS = new ByteArrayOutputStream(nheaderbytes);
        DataOutputStream headerStream = new DataOutputStream(headerBAOS);
        ByteArrayOutputStream pageBAOS = new ByteArrayOutputStream(npagebytes);
        DataOutputStream pageStream = new DataOutputStream(pageBAOS);

        boolean done = false;
        boolean first = true;

        while (!done) {
            int c = br.read();

            // 忽略 Windows 换行符 '\r'
            if (c == '\r')
                continue;

            // 判断是否是一行结束
            if (c == '\n') {
                if (first) {
                    first = true;
                    continue;
                }
                recordcount++;
                first = true;
            } else {
                first = false;
            }

            // 判断是否是字段分隔符或行结束
            if (c == fieldSeparator || c == '\n' || c == '\r') {
                String s = new String(buf, 0, curpos).trim();

                // 写入字段值到 pageStream
                if (typeAr[fieldNo] == Type.INT_TYPE) {
                    try {
                        pageStream.writeInt(Integer.parseInt(s));
                    } catch (NumberFormatException e) {
                        System.out.println("BAD LINE : " + s);
                    }
                } else if (typeAr[fieldNo] == Type.STRING_TYPE) {
                    int len = Math.min(Type.STRING_LEN, s.length());
                    pageStream.writeInt(len);
                    byte[] bytes = s.getBytes();
                    pageStream.write(bytes, 0, len);
                    // 补零填充剩余空间
                    for (int i = len; i < Type.STRING_LEN; i++)
                        pageStream.write((byte) 0);
                }

                curpos = 0;
                if (c == '\n') {
                    fieldNo = 0;
                } else {
                    fieldNo++;
                }

            } else if (c == -1) {
                done = true;
            } else {
                buf[curpos++] = (char) c;
                continue;
            }

            // 如果当前页满了 或者 已经处理完所有记录 或者 是空文件，则写入该页
            if (recordcount >= nrecords || (done && recordcount > 0) || (done && npages == 0)) {
                byte headerbyte = 0;

                // 构建 header 字节（每一位表示一个记录是否存在）
                for (int i = 0; i < nheaderbits; i++) {
                    if (i < recordcount)
                        headerbyte |= (1 << (i % 8));

                    if (((i + 1) % 8) == 0) {
                        headerStream.writeByte(headerbyte);
                        headerbyte = 0;
                    }
                }

                if ((nheaderbits % 8) > 0)
                    headerStream.writeByte(headerbyte);

                // 填充剩余空间为 0
                int padding = npagebytes - (recordcount * nrecbytes + nheaderbytes);
                for (int i = 0; i < padding; i++)
                    pageStream.writeByte(0);

                // 写入 header 和 page 到输出文件
                headerStream.flush();
                headerBAOS.writeTo(os);
                pageStream.flush();
                pageBAOS.writeTo(os);

                // 重置缓冲区准备下一页
                headerBAOS = new ByteArrayOutputStream(nheaderbytes);
                headerStream = new DataOutputStream(headerBAOS);
                pageBAOS = new ByteArrayOutputStream(npagebytes);
                pageStream = new DataOutputStream(pageBAOS);

                recordcount = 0;
                npages++;
            }
        }

        // 关闭资源
        br.close();
        os.close();
    }
}
